How To Code 16 Bit Color Graphics
Ok, I had a request to make this, so I will. I also think that a lot of the South African DOS coders out there are still using mode 13h, and I'd like to show them how cool 16 bpp color mode is. This is not to show how to optimise 16 bpp coding, it just shows the simplest ways of doing things. Optimising is something you can workout later.
Why 16 bits per pixel color?
Well it's simply very cool. Firstly you can have 65536 different colors on the screen at once, compared to 256 colors. So your pictures will look a lot cooler. Secondly it's easy, so why not? Thirdly you can do VERY cool effects very easily. Effects like transparency (multiply layers), fog, RGB lighting, shadows etc. are all much easier in this mode. Fourthly effects like smoothing, dithering, scaling etc. are much easier as well and you don't have to worry if a color is in the palette, you can just create the color. I've been a 8 bpp coder for a long time, but since I tried 16 bpp I've never gone back... (well, once).
Background
16 bit color mode is exactly that. Each pixel is 16 bits (2 bytes), as opposed to 1 byte/pixel in 256 color modes. So a 320x200 mode will require 128000 bytes of space in 16 bit color mode, compared to the normal 64000, because we have to write twice as many bytes - yet there are still 64000 pixels. I'm going to be dealing with 320x200x16bit, as I just want to introduce this concept, then you can easily extend it to other resolutions. I suggest you read a good doc on the VESA spec, as it has a lot of very cool info, like hardware scrolling, all the videomodes, how to get videocard info, etc. I'll just give you the skeleton to play with to get you started in 16 bit vmodes.
Another thing is that there is no palette! Nada. We are now working with RED, GREEN and BLUE. The 16 bit number which is each pixel is structured like this (in binary):
FEDCBA9876543210 - bit position
rrrrrggggggbbbbb - pixel
The 1st 5 bits are for how much BLUE the pixel has, the next 6 bits are for how much GREEN the pixel has, and the next 5 bits are for how much RED the pixel has. So remember that 5bits can be from 0-31, and 6bits from 0-63. So green has more of a range than red and blue. Which is cool, because of all the colors, green is the one the human eye is most sensitive to. So for example:
FEDCBA9876543210 - bit position
0000000000000000 - pixel = black
FEDCBA9876543210 - bit position
1111111111111111 - pixel = white
FEDCBA9876543210 - bit position
0000000000011111 - pixel = red
FEDCBA9876543210 - bit position
0000011111100000 - pixel = green
FEDCBA9876543210 - bit position
1111100000000000 - pixel = blue
FEDCBA9876543210 - bit position
1111100000011111 - pixel = purple
Get it?
Setting The Mode
To set the mode you need to do a BIOS interrupt. Yes it's slow, but we're only doing it once, so it's fine. You can read up on how to do more advanced things like setting up a linear frame buffer by yourself.
mov ax,4f02 ;the "setmode" function (hex)
mov bx,10e ;the video mode to set (hex)
int 10h
4f02h is the setmode function. Consult a cool VESA doc for all the other functions. 10e is the video mode number. Just like 13h is the mode number for 320x200x8bit, 10e is for 320x200x16bit. Ok, so now you are in 320x200x16bit. Now let's have some fun.
Double Buffers
It's basically essential to use a double buffer. Firstly for speed (writing to video memory is slooow), and also so that the viewer only sees the completed picture. Another very good reason is that if you aren't using LFB (linear frame buffer), you will have to deal with page (64k) boundaries. It's better to use a 128k buffer, and then every frame flip the 128k buffer to video memory, doing 64k at a time for two banks (changing the bank in between). Although I would suggest that you figure out LFB, it makes things much easier.
Putpixel
Just like a normal mode13h putpixel, except that you must remember two things when finding the correct pointer address from the X/Y coordinates.
1. Each pixel is 16bits, not 8bits, so you need to add X*2 (or add X twice).
2. Although the width of the screen is 320 pixels, you need to multiply by 640 because of the number of bytes.
mov ebx,[X]
shl ebx,1 ;X*2
mov edi,[Y]
mov edx,edi
shl edi,9
shl edx,7 ;Y*640
add edi,edx
add edi,ebx ;edi=(x*2)+(y*640)
add edi,[address] ;edi=edi+pointer to vbuffer
mov ax,[color] ;ax=16bit color value
mov [edi],ax ;write it!
Getpixel
Getpixel is VERY similar:
mov ebx,[X]
shl ebx,1 ;X*2
mov edi,[Y]
mov edx,edi
shl edi,9
shl edx,7 ;Y*640
add edi,edx
add edi,ebx ;edi=(x*2)+(y*640)
add edi,[address] ;edi=edi+pointer to vbuffer
mov ax,[edi],ax ;get it!
mov [color],ax ;ax=16bit color value
Copying To Video Memory
You see.... if you aren't using a LFB it gets complicated (well not too complex). But since I won't get into LFBs, I'll show you a simple way quickly. Since video memory is by default (on most vcards) divided into chunks of 64k, you can only write 64k. So you can only write half the screen in 320x200x16bit mode. What we have to do is this:
1. Switch to bank0
2. Copy 1st 64k
3. Switch to bank1
4. Copy 2nd 64k
That's it. Except that 128k doesn't equal 320x200x2. So 1st we write 64k (65536 bytes) and then in bank 1 we write 128000-65536=62464, which you can see:
;1st 64k
mov ax,4f05h ;VESA bank set function
mov bx,0
mov dx,0 ;bank number(0)
int 10h
mov edi,0a0000h ;we want to write to video memory
mov esi,[where] ;from this address
mov ecx,16384 ;this ammount(65536/4)
rep movsd ;write loop, 4bytes/write
;2nd 64k
mov ax, 4f05h ;VESA bank set function
mov bx,0
mov dx,1 ;bank number(1)
int 10h
mov edi,0a0000h ;we want to write to video memory
mov esi,[where] ;from this address
add esi,65536 ;plus this ammount
mov ecx,15616 ;this ammount(128000-65536)/4
rep movsd ;write loop, 4bytes/write
That will copy your 128 buffer to the screen so you can see it.
How To Deal With RGB (Transparency)
One of the most important things to be able to do (initially), is to be able to extract the RGB values from the 16bit color value. But one must remember that not ALL videocards handle 16bit modes the same. Some structure the pixels:
RGB16 rrrrrggggggbbbbb (most common)
BGR16 bbbbbggggggrrrrr (backwards)
RGB15 rrrrr0gggggbbbbb (everything is 5bits)
After reading your VESA spec, you will see how to get info from VESA calls about which type your card is using. I'll just work with RGB16 for now.
You have 5bits:RED 6bits:GREEN and 5bits:BLUE, you must extract the RGB values using masks and shifting.
Some of us had to figure all of this out by ourselves, but here it is:
mov ax,[edi] ;pixel value
mov bx,ax ;backup pixel value
;Now extract BLUE
and bx,0000000000011111b ;mask unwanted bits
mov [blue],bx ;nab it
;Now extract GREEN
mov bx,ax ;backup pixel value
and bx,0000011111100000b ;mask unwanted bits
shr bx,5 ;shift right
mov [green],bx ;nab it
;Now extract RED
mov bx,ax ;backup pixel value
and bx,1111100000000000b ;mask unwanted bits
shr bx,11 ;shift right
mov [red],bx ;nab it
Now we have red, green & blue in separate variables. Now imagine if for transparency we got the RGB values of two pixels, and did this:
r=(red1+red2)/2;
g=(green1+green2)/2;
b=(blue1+blue1)/2;
And then wrote the pixel (after recompiling the RGB into the color value):
mov ax,[r] ;get red
shl ax,11 ;shift it
mov bx,[g] ;get green
shl bx,5 ;shift it
add ax,bx ;add it to pixel
mov bx,[b] ;get blue
add ax,bx ;add it to pixel
mov [edi],ax ;write the pixel
So I recompiled the separate RGB values into one 16 bit value again, and then wrote it. So you see how you could to transparency now?
Additive
Very similiarly, what if we did:
r=(red1+red2);
g=(green1+green2);
b=(blue1+blue2);
if (r>31) r=31;
if (g>63) g=63;
if (b>31) b=31;
writepixel(r,g,b);
Then we would be doing very cool additive drawing, without overflowing. So you could layer sprites on top of each other, and then would combine their color values, until they add up to white. This can be used very effectively in explosion/particle effects.
Subtractive
Very similiarly, what if we did:
r=(red1-red2);
g=(green1-green2);
b=(blue1-blue2);
if (r<0) r=0;
if (g<0) g=0;
if (b<0) b=0;
writepixel(r,g,b);
Other Ideas
I can't go through all the possible uses. But I will just put a few thoughts into your mind. How easy would crossfading be using 16 bpp? How about a bluring effect? How about a function that makes the image negative colors? How about a function which changes the percentage of RGB in an images? How about cool transparent texturemapped 3d objects? How about lighting images using additive mapping of a lightmap? How about shadows? If you are scaling/mapping an image to a differnt size/shape - won't you be able to interpolate the colors, and add new colors to add extra detail and eliminate blockyness when zooming in close? Won't anti-aliasing be easy?
Closing Words
Hehe, I hope you understood this tutorial, and that it will help you with your coding. I might write other tutorials if people request them. I have much to learn about coding, but I am always happy to share what I do know with others. Happy coding!
Greets
The SA demoscene!
The demoscene.
Anybody who has every released source.
Anybody who has every released a tutorial.
Saurax, Rawnerve, and all the old SO members(contact me!)
Viper, Maverick, Caz, Neuron, Riot, Deadpoet, Jahya.
Celestial & workers-with
People who are hosting the sademoscene site! Thanks!
All my friends far away!
Contact Me
Things might change, so these are my email addies in order of stability. If you try one and nothing happens, try another.
andrew@overload.co.za sfeist@netactive.co.za rawhed@hotmail.com
Visit the SA demoscene site at: http://www.surf.to/demos Enter Optimise99!!!
-Rawhed/Sensory Overload -Mailto:andrew@overload.co.za -Http://www.overload.co.za -Andrew Griffiths -South Africa -05-07-1999